Skip to content

sync::mpsc: prevent double free on Drop #139553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 18, 2025

Conversation

petrosagg
Copy link
Contributor

This PR is fixing a regression introduced by #121646 that can lead to a double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR crossbeam-rs/crossbeam#1187

@rustbot
Copy link
Collaborator

rustbot commented Apr 8, 2025

r? @jhpratt

rustbot has assigned @jhpratt.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Apr 8, 2025
@taiki-e
Copy link
Member

taiki-e commented Apr 8, 2025

cc @ibraheemdev

@tgross35
Copy link
Contributor

tgross35 commented Apr 8, 2025

Is it possible add a test reproducing the failure? Ideally something ./x miri library/std ... flags without this fix so we catch any regressions.

@petrosagg
Copy link
Contributor Author

I can try to find a miri seed that leads to the problematic thread scheduling but I'm worried that if the code of the channel implementation changes or the version of miri changes the tested schedule will change as well and the test won't be testing what we think it does anymore.

@petrosagg petrosagg closed this Apr 9, 2025
@petrosagg petrosagg reopened this Apr 9, 2025
@petrosagg
Copy link
Contributor Author

I accidentally clicked the close button, sorry!

@tgross35
Copy link
Contributor

tgross35 commented Apr 9, 2025

I think it's fine to just include an isolated test that makes conditions for this problem favorable, extra good if's reasonably fast to provide a seed+sha for which Miri shows the problem locally.

Cc @RalfJung

@petrosagg
Copy link
Contributor Author

@tgross35 I have been running Miri with the nightly-2025-04-08 toolchain using the command line MIRIFLAGS="-Zmiri-many-seeds=0..1000000000" cargo miri run on the following program but no luck with a specific seed yet.

use std::sync::mpsc::channel;
use std::thread;

fn main() {
    let (tx, rx) = channel::<i32>();
    let tx2 = tx.clone();

    let t1 = thread::spawn(move || {
        let _ = tx.send(1);
    });

    tx2.send(1).unwrap();
    drop(rx);
    let _ = t1.join();
}

It is fairly easy to reproduce manually by adding a sleep at the right place and running the test of this commit adapter to std's channels crossbeam-rs/crossbeam@00dcea6.

I will leave Miri running overnight and report back with any interesting seeds.

@RalfJung
Copy link
Member

RalfJung commented Apr 10, 2025

Does the bug reproduce in Miri with the sleep? If it's easy to reproduce outside Miri, the same code show also be quite likely to reproduce it inside Miri.

Maybe we could have a -Zmiri-preemption-rate=0 test with a #[cfg(miri)] yield_now() in the right place inside the channel. However that yield_now would then always be present in the Miri sysroot version of this code...

@petrosagg
Copy link
Contributor Author

After leaving Miri running overnight it didn't find a suitable seed that produces the interleaving that causes the double free. I'm not sure why this interleaving is hard to reach.

I pushed a commit here petrosagg@79372e9 that adds a sleep to make the specific thread interleaving favorable and the problem reproduces with both normal tests and Miri. You can reproduce it with either ./x test src/tools/miri --test-args tests/pass/gh_139553.rs or ./x test library/std --test-args gh_139553

The normal test will print free(): double free detected in tcache 2 and Miri outputs the following:

error: Undefined Behavior: out-of-bounds pointer use: alloc4180 has been freed, so this pointer is dangling
  --> /home/petrosagg/projects/rust/library/alloc/src/boxed.rs:1154:9
   |
LL |         Box(unsafe { Unique::new_unchecked(raw) }, alloc)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: alloc4180 has been freed, so this pointer is dangling
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
help: alloc4180 was allocated here:
  --> tests/pass/gh_139553.rs:9:44
   |
LL |     let t1 = thread::spawn(move || assert!(s1.send(42).is_err()));
   |                                            ^^^^^^^^^^^
help: alloc4180 was deallocated here:
  --> tests/pass/gh_139553.rs:18:5
   |
LL |     drop(r);
   |     ^^^^^^^
   = note: BACKTRACE (of the first span):
   = note: inside `std::boxed::Box::<std::sync::mpmc::list::Block<u64>>::from_raw_in` at /home/petrosagg/projects/rust/library/alloc/src/boxed.rs:1154:9: 1154:58
   = note: inside `std::boxed::Box::<std::sync::mpmc::list::Block<u64>>::from_raw` at /home/petrosagg/projects/rust/library/alloc/src/boxed.rs:1045:18: 1045:48
   = note: inside `<std::sync::mpmc::list::Channel<u64> as std::ops::Drop>::drop` at /home/petrosagg/projects/rust/library/std/src/sync/mpmc/list.rs:654:22: 654:42
   = note: inside `std::ptr::drop_in_place::<std::sync::mpmc::list::Channel<u64>> - shim(Some(std::sync::mpmc::list::Channel<u64>))` at /home/petrosagg/projects/rust/library/core/src/ptr/mod.rs:523:1: 523:56
   = note: inside `std::ptr::drop_in_place::<std::sync::mpmc::counter::Counter<std::sync::mpmc::list::Channel<u64>>> - shim(Some(std::sync::mpmc::counter::Counter<std::sync::mpmc::list::Channel<u64>>))` at /home/petrosagg/projects/rust/library/core/src/ptr/mod.rs:523:1: 523:56
   = note: inside `std::ptr::drop_in_place::<std::boxed::Box<std::sync::mpmc::counter::Counter<std::sync::mpmc::list::Channel<u64>>>> - shim(Some(std::boxed::Box<std::sync::mpmc::counter::Counter<std::sync::mpmc::list::Channel<u64>>>))` at /home/petrosagg/projects/rust/library/core/src/ptr/mod.rs:523:1: 523:56
   = note: inside `std::mem::drop::<std::boxed::Box<std::sync::mpmc::counter::Counter<std::sync::mpmc::list::Channel<u64>>>>` at /home/petrosagg/projects/rust/library/core/src/mem/mod.rs:935:24: 935:25
   = note: inside `std::sync::mpmc::counter::Sender::<std::sync::mpmc::list::Channel<u64>>::release::<{closure@<std::sync::mpmc::Sender<u64> as std::ops::Drop>::drop::{closure#1}}>` at /home/petrosagg/projects/rust/library/std/src/sync/mpmc/counter.rs:65:17: 65:61
   = note: inside `<std::sync::mpmc::Sender<u64> as std::ops::Drop>::drop` at /home/petrosagg/projects/rust/library/std/src/sync/mpmc/mod.rs:633:45: 633:85
   = note: inside `std::ptr::drop_in_place::<std::sync::mpmc::Sender<u64>> - shim(Some(std::sync::mpmc::Sender<u64>))` at /home/petrosagg/projects/rust/library/core/src/ptr/mod.rs:523:1: 523:56
   = note: inside `std::ptr::drop_in_place::<std::sync::mpsc::Sender<u64>> - shim(Some(std::sync::mpsc::Sender<u64>))` at /home/petrosagg/projects/rust/library/core/src/ptr/mod.rs:523:1: 523:56
note: inside `main`
  --> tests/pass/gh_139553.rs:20:1
   |
LL | }
   | ^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

@RalfJung
Copy link
Member

I pushed a commit here petrosagg@79372e9 that adds a sleep to make the specific thread interleaving favorable

If you make this #[cfg(miri)] thread::yield_now();, and also run Miri with MIRIFLAGS="-Zmiri-preemption-rate=0", can Miri still reproduce the issue? That might need more targeted yields in more places, not sure.

@petrosagg
Copy link
Contributor Author

I will try reproducing with just yield_now() now and report back

@petrosagg
Copy link
Contributor Author

I'm not able to reproduce it even with a yield_now() call at the place where I put the sleep in the manual repro. I tried various combinations of parameters and seeds but nothing seems to trigger it.

@petrosagg petrosagg force-pushed the channel-double-free branch from 8939b53 to 19219f5 Compare April 11, 2025 10:46
@rustbot
Copy link
Collaborator

rustbot commented Apr 11, 2025

The Miri subtree was changed

cc @rust-lang/miri

@petrosagg
Copy link
Contributor Author

I managed to repro it with Miri and simple thread yields! I think the default compare_and_exchange_weak failure rate was the reason I couldn't get it to reproduce before. I have now updated the PR with two commits. The first adds the reproducer without the fix and should fail with ./x test src/tools/miri --test-args tests/pass/concurrency/issue139553.rs. The second commit adds the fix and makes the test pass.

cc @RalfJung @tgross35

@saethlin
Copy link
Member

saethlin commented Apr 11, 2025

I think the default compare_and_exchange_weak failure rate was the reason I couldn't get it to reproduce before.

Is it your estimation that the default failure rate is too high, or is it too low?

@petrosagg
Copy link
Contributor Author

Is it your estimation that the default failure rate is too high, or is it too low?

Too high. My test is basically baking a thread schedule (by calling yield_now()) that only triggers the bug if the compare_and_exchange_weak in start_send doesn't spuriously fail. So any non-zero probability of spurious failure will mess up the intended schedule.

That said, the overnight test looking for seed values had only one yield_now() call whereas the test that I have committed now has one more in the test itself. That means that I had to be extra lucky for the random schedule to pick the appropriate points for yielding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have tests/pass/issues for tests testing specific issues. Also please put a comment in the file explaining the context of the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I totally missed that directory. I moved the test there and added detailed comments explaining the particular interleaving and the way the double free is caused.

@@ -213,6 +213,8 @@ impl<T> Channel<T> {
.compare_exchange(block, new, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
#[cfg(miri)]
crate::thread::yield_now();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this is that this will then always be present when Miri executes this code, even outside the test. Ideally we'd only enable this on our CI. This will require some new flag though...

Also there should be a comment explaining the point of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment explaining why this point is interesting and a pointer to the test that uses it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

I guess now the main question is whether it's worth leaving in this yield for all Miri users or whether it should be somehow only applied for the test suite. It probably doesn't harm to always have it...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also lean towards leaving there. It does yield at a very interesting point of the channel in general so having it there might catch future regressions sooner

@petrosagg petrosagg force-pushed the channel-double-free branch from 19219f5 to 63fafe3 Compare April 11, 2025 12:19
@petrosagg petrosagg force-pushed the channel-double-free branch from 63fafe3 to 72c9a47 Compare April 11, 2025 12:29
This PR is fixing a regression introduced by rust-lang#121646 that can lead to a
double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR
crossbeam-rs/crossbeam#1187

Signed-off-by: Petros Angelatos <[email protected]>
@petrosagg petrosagg force-pushed the channel-double-free branch from 72c9a47 to b9e2ac5 Compare April 11, 2025 12:33
@rustbot rustbot assigned RalfJung and unassigned jhpratt Apr 16, 2025
@rustbot rustbot added the beta-nominated Nominated for backporting to the compiler in the beta channel. label Apr 16, 2025
@RalfJung
Copy link
Member

Miri changes LGTM. @tgross35 I am going to take this as an "r=me for library changes" from your side.

@bors r=RalfJung,tgross35

@bors
Copy link
Collaborator

bors commented Apr 17, 2025

📌 Commit b9e2ac5 has been approved by RalfJung,tgross35

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 17, 2025
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Apr 17, 2025
…alfJung,tgross35

sync::mpsc: prevent double free on `Drop`

This PR is fixing a regression introduced by rust-lang#121646 that can lead to a double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR crossbeam-rs/crossbeam#1187
@ibraheemdev
Copy link
Member

Sorry I'm very busy as of late, but from a cursory look this seems good.

bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 17, 2025
…iaskrgr

Rollup of 9 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#138934 (support config extensions)
 - rust-lang#139114 (Implement `pin!()` using `super let`)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Apr 17, 2025
…alfJung,tgross35

sync::mpsc: prevent double free on `Drop`

This PR is fixing a regression introduced by rust-lang#121646 that can lead to a double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR crossbeam-rs/crossbeam#1187
@Amanieu Amanieu added the beta-accepted Accepted for backporting to the compiler in the beta channel. label Apr 17, 2025
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 17, 2025
…iaskrgr

Rollup of 8 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#138934 (support config extensions)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 18, 2025
…iaskrgr

Rollup of 8 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#138934 (support config extensions)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 18, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 18, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 18, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 5e8bc7f into rust-lang:master Apr 18, 2025
6 checks passed
@rustbot rustbot added this to the 1.88.0 milestone Apr 18, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Apr 18, 2025
Rollup merge of rust-lang#139553 - petrosagg:channel-double-free, r=RalfJung,tgross35

sync::mpsc: prevent double free on `Drop`

This PR is fixing a regression introduced by rust-lang#121646 that can lead to a double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR crossbeam-rs/crossbeam#1187
@cuviper cuviper mentioned this pull request Apr 18, 2025
@cuviper cuviper modified the milestones: 1.88.0, 1.87.0 Apr 18, 2025
@cuviper cuviper removed the beta-nominated Nominated for backporting to the compiler in the beta channel. label Apr 18, 2025
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 19, 2025
[beta] backports

- Fix 2024 edition doctest panic output rust-lang#139328
- make `Arguments::as_statically_known_str` doc(hidden) rust-lang#139389
- Revert "Deduplicate template parameter creation" rust-lang#139878
- sync::mpsc: prevent double free on `Drop` rust-lang#139553

r? cuviper
github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this pull request Apr 19, 2025
…alfJung,tgross35

sync::mpsc: prevent double free on `Drop`

This PR is fixing a regression introduced by rust-lang#121646 that can lead to a double free when dropping the channel.

The details of the bug can be found in the corresponding crossbeam PR crossbeam-rs/crossbeam#1187
github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this pull request Apr 19, 2025
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#138528 (deref patterns: implement implicit deref patterns)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139553 (sync::mpsc: prevent double free on `Drop`)
 - rust-lang#139615 (Remove `name_or_empty`)
 - rust-lang#139853 (Disable combining LLD with external llvm-config)
 - rust-lang#139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - rust-lang#139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
beta-accepted Accepted for backporting to the compiler in the beta channel. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.